本篇是實作常用的 AWS IAM 服務之 Terraform 模組,並且會使用到 YAML 資料結構來定義模組的內容,完整的專案程式碼分享在我的 Github 上。
./configs/iam/iam.yaml
與五個放置 policy JSON 檔案位置的目錄:
./configs/iam/assume_role_policies
: 放置 assume role policy 的目錄./configs/iam/group_policies
: 放置 inline group policy 的目錄./configs/iam/policies
: 放置 policy 的目錄./configs/iam/role_policies
: 放置 inline role policy 的目錄./configs/iam/user_policies
: 放置 inline user policy 的目錄my_iam
的放置位置 modules/my_iam
:├── configs
│ ├── iam
│ │ ├── assume_role_policies
│ │ ├── group_policies
│ │ ├── policies
│ │ ├── role_policies
│ │ ├── user_policies
│ │ └── iam.yaml
│ ├── subnet
│ └── vpc
├── example.tfvars
├── locals.tf
├── main.tf
├── modules
│ ├── my_iam
│ │ ├── iam_group.tf
│ │ ├── iam_group_policy.tf
│ │ ├── iam_group_policy_attachment.tf
│ │ ├── iam_instance_profile.tf
│ │ ├── iam_policy.tf
│ │ ├── iam_role.tf
│ │ ├── iam_role_policy.tf
│ │ ├── iam_role_policy_attachment.tf
│ │ ├── iam_user.tf
│ │ ├── iam_user_group_membership.tf
│ │ ├── iam_user_policy.tf
│ │ ├── iam_user_policy_attachment.tf
│ │ ├── output.tf
│ │ ├── provider.tf
│ │ └── variables.tf
│ ├── my_igw
│ ├── my_instances
│ ├── my_nacls
│ ├── my_route_tables
│ ├── my_subnets
│ └── my_vpc
└── variables.tf
./configs/iam/iam.yaml
內容來定義 IAM 需要用建立的資源:# Define Policy
policies: []
# Emample:
# - name: "The name of policy"
# description: "The description of policy"
# json_file: ./configs/iam/policies/???????.json
# path: "/"
# Define user, bind user group and attch existing policy
users: []
# Example:
# - name: "The name of user"
# groups:
# - "The name of group"
# inline_policies:
# - name: "The name of inline policy"
# - json_file: ./configs/iam/user_policies/???????.json
# policy_arns: []
# - arn:aws:iam::????????????:policy/???????
# Define user group, attch inline and existing policies
groups: []
# Example
# - name: "The name of group"
# inline_policies:
# - name: "The name of inline policy"
# - json_file: ./configs/iam/group_policies/???????.json
# policy_arns:
# - arn:aws:iam::????????????:policy/???????
# Define role, attch inline and existing policies
roles: []
# Example
# - name: "The name of role"
# description: "The description of role"
# assume_role_policy_json_file: ./configs/iam/assume_role_policies/???????.json
# inline_policies:
# - name: "The name of inline policy"
# - json_file: ./configs/iam/role_policies/???????.json
# policy_arns: []
# - arn:aws:iam::????????????:policy/???????
# tag_name: ""
# path: "/"
# Define instance profiles
instance_profiles: []
# Example
# - name: "The name of instance profile"
# role: "The role name of instance profile"
my_iam
模組./modules/my_iam/outputs.tf
:output "iam_group_arn" {
value = aws_iam_group.groups
}
output "iam_role_arn" {
value = aws_iam_role.roles
}
output "iam_user_arn" {
value = aws_iam_user.users
}
./modules/my_iam/provider.tf
:provider "aws" {
region = var.aws_region
profile = var.aws_profile
}
./modules/my_iam/variables.tf
:variable "aws_region" {
description = "AWS region"
default = "ap-northeast-1"
}
variable "aws_profile" {
description = "AWS profile"
default = ""
}
variable "project_name" {
type = string
description = "Project name"
default = ""
}
variable "department_name" {
type = string
description = "Department name"
default = "SRE"
}
variable "iam_path" {
type = string
default = ""
}
./modules/my_iam/iam_group.tf
:iam_path
為傳入的 ./configs/iam/iam.yaml
設定檔路徑位址,透過 yamldecode
取出 key 值 groups
的 value 為一 list 物件
locals {
groups = yamldecode(file("${var.iam_path}"))["groups"]
}
resource "aws_iam_group" "groups" {
for_each = { for r in local.groups : r.name => r }
name = each.value.name
path = "/"
}
./modules/my_iam/iam_group_policy.tf
:flattern
函式將 group.name
與 inline_policies
轉換成單一列表
locals {
group_policies = flatten([
for group in local.groups : [
for inline_policy in lookup(group, "inline_policies", []) : {
name = inline_policy.name
json_file = inline_policy.json_file
group = group.name
}
]
])
}
resource "aws_iam_group_policy" "group_policies" {
for_each = { for r in local.group_policies : "${r.group}:${r.name}" => r }
name = each.value.name
policy = file("${each.value.json_file}")
group = each.value.group
depends_on = [
aws_iam_group.groups
]
}
./modules/my_iam/iam_group_policy_attachment.tf
:flattern
函式將 group.name
與 policy_arns
轉換成單一列表
locals {
group_policy_association_list = flatten([
for group in local.groups : [
for policy_arn in lookup(group, "policy_arns", []) : {
group = group.name
policy_arn = policy_arn
}
]
])
}
resource "aws_iam_group_policy_attachment" "attachments" {
for_each = { for r in local.group_policy_association_list : "${r.group}/${r.policy_arn}" => r }
policy_arn = each.value.policy_arn
group = each.value.group
depends_on = [
aws_iam_policy.policies,
aws_iam_group.groups
]
}
./modules/my_iam/iam_instance_profile.tf
:iam_path
為傳入的 ./configs/iam/iam.yaml
設定檔路徑位址,透過 yamldecode
取出 key 值 instance_profiles
的 value 為一 list 物件
locals {
instance_profiles = yamldecode(file("${var.iam_path}"))["instance_profiles"]
}
resource "aws_iam_instance_profile" "instance_profile" {
for_each = { for r in local.instance_profiles : r.name => r }
name = each.value.name
role = each.value.role
tags = {
Department = var.department_name
Name = each.value.name
Project = var.project_name
}
tags_all = {
Department = var.department_name
Name = each.value.name
Project = var.project_name
}
}
./modules/my_iam/iam_policy.tf
:iam_path
為傳入的 ./configs/iam/iam.yaml
設定檔路徑位址,透過 yamldecode
取出 key 值 policies
的 value 為一 list 物件
locals {
policies = yamldecode(file("${var.iam_path}"))["policies"]
}
resource "aws_iam_policy" "policies" {
for_each = { for r in local.policies : r.name => r }
description = each.value.description
name = each.value.name
path = each.value.path
policy = file("${each.value.json_file}")
tags = {
Department = var.department_name
Project = var.project_name
}
tags_all = {
Department = var.department_name
Project = var.project_name
}
}
./modules/my_iam/iam_role.tf
:iam_path
為傳入的 ./configs/iam/iam.yaml
設定檔路徑位址,透過 yamldecode
取出 key 值 roles
的 value 為一 list 物件
locals {
roles = yamldecode(file("${var.iam_path}"))["roles"]
}
resource "aws_iam_role" "roles" {
for_each = { for r in local.roles : r.name => r }
assume_role_policy = each.value.assume_role_policy_json_file != "" ? file("${each.value.assume_role_policy_json_file}") : <<POLICY
{}
POLICY
description = each.value.description
max_session_duration = "3600"
name = each.value.name
path = each.value.path
tags = {
Department = var.department_name
Name = each.value.tag_name
Project = var.project_name
}
tags_all = {
Department = var.department_name
Name = each.value.tag_name
Project = var.project_name
}
}
./modules/my_iam/iam_role_policy.tf
:flattern
函式將 role.name
與 inline_policies
轉換成單一列表
locals {
role_policies = flatten([
for role in local.roles : [
for inline_policy in lookup(role, "inline_policies", []) : {
name = inline_policy.name
json_file = inline_policy.json_file
role = role.name
}
]
])
}
resource "aws_iam_role_policy" "role_policies" {
for_each = { for r in local.role_policies : "${r.role}:${r.name}" => r }
name = each.value.name
policy = file("${each.value.json_file}")
role = each.value.role
depends_on = [
aws_iam_role.roles
]
}
./modules/my_iam/iam_role_policy_attachment.tf
:flattern
函式將 role.name
與 policy_arns
轉換成單一列表
locals {
role_policy_association_list = flatten([
for role in local.roles : [
for policy_arn in lookup(role, "policy_arns", []) : {
role = role.name
policy_arn = policy_arn
}
]
])
}
resource "aws_iam_role_policy_attachment" "attachments" {
for_each = { for r in local.role_policy_association_list : "${r.role}/${r.policy_arn}" => r }
policy_arn = each.value.policy_arn
role = each.value.role
depends_on = [
aws_iam_policy.policies,
aws_iam_role.roles
]
}
./modules/my_iam/iam_user.tf
:iam_path
為傳入的 ./configs/iam/iam.yaml
設定檔路徑位址,透過 yamldecode
取出 key 值 users
的 value 為一 list 物件
locals {
users = yamldecode(file("${var.iam_path}"))["users"]
}
resource "aws_iam_user" "users" {
# checkov:skip= CKV_AWS_273: need to do it later
for_each = { for r in local.users : r.name => r }
name = each.value.name
path = "/"
tags = {
Department = var.department_name
Project = var.project_name
}
tags_all = {
Department = var.department_name
Project = var.project_name
}
}
./modules/my_iam/iam_user_group_membership.tf
:iam_path
為傳入的 ./configs/iam/iam.yaml
設定檔路徑位址,透過 yamldecode
取出 key 值 users
的 value 為一 list 物件
locals {
user_groups = yamldecode(file("${var.iam_path}"))["users"]
}
resource "aws_iam_user_group_membership" "user_groups" {
for_each = { for r in local.user_groups : r.name => r if length(r.groups) > 0 }
groups = each.value.groups
user = each.value.name
depends_on = [
aws_iam_user.users
]
}
./modules/my_iam/iam_user_policy.tf
:flattern
函式將 user.name
與 inline_polices
轉換成單一列表
locals {
user_policies = flatten([
for user in local.users : [
for inline_policy in lookup(user, "inline_policies", []) : {
name = inline_policy.name
json_file = inline_policy.json_file
user = user.name
}
]
])
}
resource "aws_iam_user_policy" "user_policies" {
for_each = { for r in local.user_policies : "${r.user}:${r.name}" => r }
name = each.value.name
policy = file("${each.value.json_file}")
user = each.value.user
depends_on = [
aws_iam_user.users
]
}
./modules/my_iam/iam_user_policy_attachment.tf
:flattern
函式將 user.name
與 policy_arns
轉換成單一列表
locals {
user_policy_association_list = flatten([
for user in local.users : [
for policy_arn in lookup(user, "policy_arns", []) : {
user = user.name
policy_arn = policy_arn
}
]
])
}
resource "aws_iam_user_policy_attachment" "attachments" {
for_each = { for r in local.user_policy_association_list : "${r.user}/${r.policy_arn}" => r }
policy_arn = each.value.policy_arn
user = each.value.user
depends_on = [
aws_iam_policy.policies,
aws_iam_user.users
]
}
example.tfvars
:aws_region="ap-northeast-1"
aws_profile="<YOUR_PROFILE>"
project_name="example"
department_name="SRE"
main.tf
:terraform {
required_providers {
aws = {
version = "5.15.0"
}
}
backend "s3" {
bucket = "<YOUR_S3_BUCKET_NAME>"
dynamodb_table = "<YOUR_DYNAMODB_TABLE_NAME>"
key = "terraform.tfstate"
region = "ap-northeast-1"
shared_credentials_file = "~/.aws/config"
profile = "<YOUR_PROFILE>"
}
}
# 其他模組省略
# iam
module "iam" {
aws_profile = var.aws_profile
aws_region = var.aws_region
department_name = var.department_name
project_name = var.project_name
iam_path = "./configs/iam/iam.yaml"
source = "./modules/my_iam"
}
# Define Policy
policies: []
# Define user, bind user group and attch existing policy
users:
- name: admin-user
groups: []
inline_policies:
- name: admin_access
json_file: "./configs/iam/user_policies/admin_access.json"
policy_arns: []
# Define user group, attch inline and existing policies
groups: []
# Define role, attch inline and existing policies
roles: []
instance_profiles: []
terraform init && terraform plan --out .plan -var-file=example.tfvars
來確認一下結果: # module.iam.aws_iam_user.users["admin-user"] will be created
+ resource "aws_iam_user" "users" {
+ arn = (known after apply)
+ force_destroy = false
+ id = (known after apply)
+ name = "admin-user"
+ path = "/"
+ tags = {
+ "Department" = "SRE"
+ "Project" = "example"
}
+ tags_all = {
+ "Department" = "SRE"
+ "Project" = "example"
}
+ unique_id = (known after apply)
}
# module.iam.aws_iam_user_policy.user_policies["admin-user:admin_access"] will be created
+ resource "aws_iam_user_policy" "user_policies" {
+ id = (known after apply)
+ name = "admin_access"
+ policy = jsonencode(
{
+ Statement = [
+ {
+ Action = "*"
+ Effect = "Allow"
+ Resource = "*"
},
]
+ Version = "2012-10-17"
}
)
+ user = "admin-user"
}
下一篇文章將會展示實作 AWS S3 之 Terraform 模組。